package com.sh.data.engine.domain.shims.mongo.manager;

import com.google.common.collect.Lists;
import com.mongodb.MongoClient;
import com.mongodb.*;
import com.mongodb.client.*;
import com.sh.data.engine.domain.shims.db.model.FieldInfoDomain;
import com.sh.data.engine.domain.shims.db.model.PreviewDataDomain;
import com.sh.data.engine.domain.shims.mongo.MongoOptions;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;

import java.sql.SQLException;
import java.util.*;

/**
 * @author: mengzheng.mhc
 * @date: 2024/6/11 16:18
 */
public class MongoManager {

    private MongoOptions options;

    public MongoManager(MongoOptions options) {
        this.options = options;
    }

    public MongoClient getMongoClient() {
        Objects.requireNonNull(this.options, "mongoOptions is null");
        MongoClientOptions options =
            MongoClientOptions.builder()
                .serverSelectionTimeout(this.options.getTimeout() * 1000)
                .build();

        if (StringUtils.isBlank(this.options.getUsername()) && StringUtils.isBlank(this.options.getPassword())) {
            return new MongoClient(parseServerAddress(this.options.getAddressList()), options);
        }

        MongoCredential credential =
            MongoCredential.createCredential(
                this.options.getUsername(),
                this.options.getAuthDbName(),
                this.options.getPassword().toCharArray());
        return new MongoClient(parseServerAddress(this.options.getAddressList()), credential, options);
    }

    /**
     * 转换为mongo地址协议
     *
     * @param rawAddressList
     * @return
     */
    private List<ServerAddress> parseServerAddress(List<String> rawAddressList) {
        if (CollectionUtils.isEmpty(rawAddressList)) {
            return Collections.emptyList();
        }
        List<ServerAddress> addressList = new LinkedList<>();
        for (String address : rawAddressList) {
            String[] tempAddress = address.split(":");
            addressList.add(new ServerAddress(tempAddress[0], Integer.parseInt(tempAddress[1])));
        }
        return addressList;
    }

    public void testConnection() {
        try (MongoClient mongoClient = getMongoClient()) {
            MongoDatabase database = mongoClient.getDatabase(this.options.getDbName());
            MongoIterable<String> mongoIterable = database.listCollectionNames();
            try (MongoCursor<String> iterator = mongoIterable.iterator();) {
            }
        } catch (MongoTimeoutException | MongoSecurityException e) {
            throw new MongoClientException(toReadableMsg(e), e);
        }
    }

    private static String toReadableMsg(MongoClientException e) {
        if (e instanceof MongoTimeoutException) {
            return "连接失败，请检查数据库服务是否可用或数据库链接是否正确";
        }
        return "连接失败，请检查用户名、密码、数据库名等参数是否正确";
    }

    /**
     * 获取集合列表
     *
     * @return
     * @throws SQLException
     */
    public List<String> getCollectionList() {
        List<String> collections = new ArrayList<>();

        try (MongoClient client = getMongoClient()) {
            final MongoIterable<String> collectionNames =
                client.getDatabase(this.options.getDbName()).listCollectionNames();
            final MongoCursor<String> iterator = collectionNames.iterator();
            while (iterator.hasNext()) {
                collections.add(iterator.next());
            }
        }
        return collections;
    }

    public List<FieldInfoDomain> getFieldList(String dbName, String collectionName) {
        List<FieldInfoDomain> fieldInfoList = new ArrayList<>();
        try (MongoClient mongoClient = getMongoClient()) {
            MongoDatabase database = mongoClient.getDatabase(dbName);
            MongoCollection<Document> collection = database.getCollection(collectionName);
            FindIterable<Document> findIterable = collection.find().limit(10);
            MongoCursor<Document> iterator = findIterable.iterator();
            while (iterator.hasNext()) {
                Document next = iterator.next();
                next.forEach(
                    (k, v) -> {
                        Optional<FieldInfoDomain> optionalInfo =
                            fieldInfoList.stream().filter(item -> item.getFieldName().equals(k)).findFirst();
                        if (!optionalInfo.isPresent()) {
                            String fieldType = v == null ? "" : v.getClass().getSimpleName();
                            FieldInfoDomain fieldInfo = new FieldInfoDomain();
                            fieldInfo.setFieldName(k);
                            fieldInfo.setFieldType(fieldType);
                            if ("_id".equals(k)) {
                                fieldInfo.setPk(true);
                            }
                            fieldInfoList.add(fieldInfo);
                        }
                    });
            }
        }
        return fieldInfoList;
    }

    public PreviewDataDomain previewData(String collectionName) {
        List<Map<String, String>> mapList = new ArrayList();
        Set<String> columns = new TreeSet<>();
        try (MongoClient client = getMongoClient()) {
            MongoDatabase database = client.getDatabase(this.options.getDbName());
            MongoCollection<Document> collection = database.getCollection(collectionName);
            FindIterable<Document> findIterable = collection.find().limit(10);
            try (MongoCursor<Document> iterator = findIterable.iterator()) {
                while (iterator.hasNext()) {
                    Document next = iterator.next();
                    Map<String, String> rawData = new HashMap<>();
                    next.forEach(
                        (k, v) -> {
                            columns.add(k);
                            rawData.put(k, v == null ? "" : v.toString());
                        });
                    mapList.add(rawData);
                }
            }
        }

        if (CollectionUtils.isEmpty(mapList)) {
            return PreviewDataDomain.builder()
                .fieldNameList(Collections.emptyList())
                .dataList(Collections.emptyList())
                .build();
        }

        List<String> fieldNameList = new ArrayList<>();
        fieldNameList.add("_id");
        columns.remove("_id");
        fieldNameList.addAll(columns);

        List<List<Object>> dataList = new LinkedList<>();
        for (Map<String, String> map : mapList) {
            List<Object> rowValueList = new LinkedList<>();
            for (String column : fieldNameList) {
                rowValueList.add(map.getOrDefault(column, ""));
            }
            dataList.add(rowValueList);
        }

        return PreviewDataDomain.builder().fieldNameList(fieldNameList).dataList(dataList).build();
    }

    public static void main(String[] args) {
        MongoOptions options = new MongoOptions();
        options.setAddressList(Lists.newArrayList("10.88.36.187:27017"));
        options.setUsername("hufu");
        options.setPassword("123456");
        options.setAuthDbName("hufu");
        options.setDbName("hufu");

        MongoManager manager = new MongoManager(options);
        final List<String> collectionList = manager.getCollectionList();
        collectionList.forEach(System.out::println);

        final PreviewDataDomain previewDataDomain = manager.previewData("menu");
        System.out.println(previewDataDomain);
    }
}
